package agentspeakeditor.editors.common.scanner;

import java.util.ArrayList;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.ITokenScanner;

import agentspeakeditor.editors.common.*;
import agentspeakeditor.editors.common.parser.Parser;
import agentspeakeditor.editors.ColorManager;
import agentspeakeditor.editors.IASLColorConstants;

public class Scanner implements ITokenScanner{

	private IDocument document;
	private int offset;
	private int position;
	private int rangeEnd;
	private int tokenOffset;
	private int currentLength;
	private int indexToken;
	
	// The last char read from the source code
	private char currentChar;
	// The kind of the current token
	private TokenType currentKind;
	// Buffer to append characters read from file
	private StringBuffer currentSpelling;
	// Current line and column in the source file
	private int line, column;
	
	private ArrayList<Token> tokens;
	
	private org.eclipse.jface.text.rules.Token defReturnToken;
	
	private ColorManager manager;
	private boolean aslParserCall;
	
	
	/**
	 * Default constructor
	 * @throws BadLocationException 
	 */
	public Scanner(ColorManager manager){			
		this.line = 0;
		this.column = 0;
		this.manager = manager;
	}
	
	public Scanner(ColorManager manager, boolean aslParserCall){			
		this.line = 0;
		this.column = 0;
		this.manager = manager;
		this.aslParserCall = aslParserCall;
	}
	
	/**
	 * Returns if a character is a separator
	 * @param c
	 * @return
	 */
	private boolean isSeparator(char c) {
		if ( c == '#' || c == ' ' || c == '\t' || c == '\r' || c == '\n') {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Reads (and ignores) a separator
	 * @throws LexicalException
	 * @throws BadLocationException 
	 */ //TODO
	private int scanSeparator() throws BadLocationException {
		int spaceCount = 0;
		while (isSeparator(this.currentChar)) {
	    // If it is a comment line
			if (this.currentChar == '#') {
				// Gets next char
				getNextChar();
				// Reads characters while they are graphics or '\t'
				while ((isGraphic(this.currentChar)) || (this.currentChar == '\t') ) {
					getNextChar();
				}
				// A command line should finish with a \n
				if (this.currentChar == '\n') {
					getNextChar();                
				}
			}
			else {
				spaceCount++;
				getNextChar();
			}
		}
		return spaceCount;
	}
	
	/**
	 * Gets the next char
	 * @throws BadLocationException 
	 */
	private void getNextChar() throws BadLocationException {
		try{
			if (offset < rangeEnd) {
				try {					
					this.currentSpelling.append(this.currentChar);
					currentChar =  document.getChar(offset);					
				} catch (BadLocationException e) {
				}
			}
			else if (offset == rangeEnd){
				this.currentSpelling.append(this.currentChar);
				currentChar = '\0';
			}
			else{
				currentChar = '\0';
			}
		}
		finally{
			offset++;
		}
			

	}
	
	/**
	 * Returns if a char is a digit (between 0 and 9)
	 * @param c
	 * @return
	 */
	private boolean isDigit(char c) {
		if ( c >= '0' && c <= '9' ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns if a char is a letter (between a and z or between A and Z)
	 * @param c
	 * @return
	 */
	private boolean isLetter(char c) {
		if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns if a char is a graphic (any ASCII visible character)
	 * @param c
	 * @return
	 */
	private boolean isGraphic(char c) {
		if ( c >= ' ' && c <= '~' ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Scans the next token
	 * Simulates the DFA that recognizes the language described by the lexical grammar
	 * @return
	 * @throws LexicalException
	 * @throws BadLocationException 
	 */ //TODO
	private TokenType scanToken() throws BadLocationException {
		// The initial automata state is 0
		// While loop to simulate the automata
		int estado = 0;
		
		while (true) {
			//System.out.println("currentChar = "+c);
			switch(estado) {
				case 0:
					  if(isLetter(currentChar)) {
						  getNextChar();
						  estado = 1; //ID
						  break;
					  }else if (isDigit(currentChar)) {
						  getNextChar();
						  estado = 2; //NUM
						  break;
					  }else if (currentChar == '+') {
						  getNextChar();
						  estado = 3; //MAIS
						  break;
					  }else if (currentChar == '-') {
						 getNextChar();
						 estado = 4; //MENOS
						 break;
					  }else if (currentChar == '>') {
						  getNextChar();
						  estado = 5; //MAIOR
						  break;
					  }else if (currentChar == '<') {
						  getNextChar();
						  estado = 6; //MENOR
						  break;
					  }else if (currentChar == '=') {
						  getNextChar();
						  estado = 7; //IGUAL
						  break;
					  }else if (currentChar == '(') {
						  getNextChar();
						  estado = 8; //PARE
						  break;
					  }else if (currentChar == ')') {
						  getNextChar();
						  estado = 9; //PARD
						  break;
					  }else if (currentChar == '\0') {
						  getNextChar();
						  estado = 10; //EOT
						  break;
					  }else if  (currentChar == '\n') {
						  getNextChar();
						  estado = 11; // EOL
						  break;
					  }else if  (currentChar == '!') {
						  getNextChar();
						  estado = 12; // EXC
						  break;
					  }else if  (currentChar == '?') {
						  getNextChar();
						  estado = 13; // INT
						  break;
					  }else if  (currentChar == '.') {
						  getNextChar();
						  estado = 14; // PON
						  break;
					  }else if  (currentChar == ';') {
						  getNextChar();
						  estado = 15; // PVIR
						  break;
					  }else if  (currentChar == ',') {
						  getNextChar();
						  estado = 16; //VIR
						  break;
					  }else if  (currentChar == '&') {
						  getNextChar();
						  estado = 17; //E
						  break;
					  }else if  (currentChar == '|') {
						  getNextChar();
						  estado = 18; //OU
						  break;
					  }else if  (currentChar == ':') {
						  getNextChar();
						  estado = 19; //DPON
						  break;
					  } else if (currentChar == '/'){
						  getNextChar();
						  estado = 20;
						  break;
					  } else if (currentChar == '*') {
						  getNextChar();
						  estado = 21;
						  break;
					  } else if (currentChar == '\r') {
						  getNextChar();
						  estado = 22;
						  break;
					  } else {
						  getNextChar();
						  estado = 23;
						  break;
					  }
				case 1:
					if(isLetter(currentChar) || isDigit(currentChar)){
						getNextChar();
						break;
					}else {
						if (this.currentSpelling.toString().equals("not")) {
							return TokenType.NOT;
						} else if (this.currentSpelling.toString().equals("true")) {
								return TokenType.TRUE;
						} else if (this.currentSpelling.toString().equals("false")) {
							return TokenType.FALSE;
						}else {
							return TokenType.ID;
						}
					}
				case 2:
					if(isDigit(currentChar)){
						getNextChar();
						break;
					}else{
						return TokenType.NUM;
					}
				case 3:
					if(currentChar == '!'){
						getNextChar();
						return TokenType.MAIS_EXC;
					}						
					else if(currentChar == '?'){
						getNextChar();
						return TokenType.MAIS_INT;
					}
					else
						return TokenType.MAIS;
				case 4:
					if(currentChar == '!'){
						getNextChar();
						return TokenType.MENOS_EXC;
					}					
					else if(currentChar == '?'){
						getNextChar();
						return TokenType.MENOS_INT;
					}
					else
						return TokenType.MENOS;
				case 5:
					if (currentChar=='=') {
						getNextChar();
						return TokenType.MAIG;
					} else {
						return TokenType.MAIOR;
					}
				case 6:
					if (currentChar=='=') {
						getNextChar();
						return TokenType.MEIG;
					} else if (currentChar=='>') {
						getNextChar();
						return TokenType.DIF;
					} else if (currentChar=='-') {
						getNextChar();
						return TokenType.SET;
					} else {
						return TokenType.MENOR;
					}
				case 7:
					if(currentChar=='='){
						getNextChar();
						return TokenType.IG;
					}else {
						return TokenType.UNDEFINED;
					}
				case 8:
					return TokenType.PARE;
				case 9:
					return TokenType.PARD;
				case 10:
					return TokenType.EOT;
				case 11:
					return TokenType.EOL;
				case 12:
					return TokenType.EXC;
				case 13:
					return TokenType.INT;
				case 14:
					return TokenType.PON;
				case 15:
					return TokenType.PVIR;
				case 16:
					return TokenType.VIR;
				case 17:
					return TokenType.E;
				case 18:
					return TokenType.OU;
				case 19:
					return TokenType.DPON;
				case 20:
					return TokenType.BARRA;
				case 21:
					return TokenType.AST;
				case 22:
					return TokenType.CR;
				default:
					return TokenType.UNDEFINED;
			}
		}
	}
	
	@Override
	public void setRange(IDocument document, int offset, int length) {
		this.document = document;
		this.offset = offset;
		rangeEnd = offset+length;	
		indexToken = 0;
		try {
			currentChar = document.getChar(offset);
			this.offset++;
		} catch (BadLocationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if(!aslParserCall){
			Parser parser = new Parser(this);
			tokens = parser.parse();
		}
	}

	@Override
	public IToken nextToken() {		
		Token token = null;
		
		if(tokens != null && indexToken < tokens.size()){
			token = tokens.get(indexToken++);
			tokenOffset = token.getOffset();
			currentLength = token.getLength();
		}
		
		return token;
	}
	
	public IToken getNextToken(){
		int spaceCount = 0;
		tokenOffset= offset-1;
		// Initializes the string buffer
		this.currentSpelling = new StringBuffer("");
		
		// Ignores separators
		try {
			spaceCount = this.scanSeparator();
		} catch (BadLocationException e) {
			e.printStackTrace();
		}
			
		
		// Clears the string buffer
		this.currentSpelling = new StringBuffer("");
		
		// Scans the next token
		try {
			this.currentKind = this.scanToken();
		} catch (BadLocationException e) {
			e.printStackTrace();
		}
				
		Token token = new Token(this.currentKind, this.currentSpelling.toString(), this.line, this.column, null);
		token.setLength(this.currentSpelling.length() + spaceCount);
		token.setOffset(tokenOffset);
		
		return 	token;
	}

	@Override
	public int getTokenOffset() {
		return tokenOffset;
	}

	@Override
	public int getTokenLength() {
		return currentLength;
	}
	
	public void setDefaultReturnToken(org.eclipse.jface.text.rules.Token token){
		this.defReturnToken = token;
	}

	public ColorManager getManager() {
		return manager;
	}
	
	
}